Skip to content

fix hook allocation and cross-platform ptrace safety#84

Merged
StarrFox merged 9 commits into
mainfrom
fix/ci-hook-failures
Jul 21, 2026
Merged

fix hook allocation and cross-platform ptrace safety#84
StarrFox merged 9 commits into
mainfrom
fix/ci-hook-failures

Conversation

@StarrFox

Copy link
Copy Markdown
Owner

Summary

  • Windows WinError 5: VirtualAllocEx was called with preferred_start=target_address, but that address is inside inject_target.exe's already-committed code section. The fix adds _find_near_free_address which uses VirtualQueryEx to find the nearest free region within ±2GB, then allocates there with MEM_RESERVE | MEM_COMMIT.
  • allocation_size undercount: sum(map(len, hook_instructions)) only counts decoded (stolen) instruction bytes because Instruction.create_* objects always return len() == 0. The fix does a first-pass instructions_to_code at target_address to get the true encoded byte count before allocating.
  • _poke_bytes corruption: When writing a non-multiple-of-8 number of bytes via PTRACE_POKETEXT, the last partial word was zero-padded, silently overwriting bytes past the intended write range. The fix reads the existing word and merges only the bytes being written.
  • Silent shellcode failures: _ptrace_exec didn't verify that the shellcode hit int3 (SIGTRAP); any other signal (e.g. SIGSEGV) would silently return a garbage RAX. Added the same SIGTRAP check that inject_so already had.

Test plan

  • Windows CI: test_create_capture_hook should no longer fail with PermissionError: [WinError 5] Access is denied
  • Linux CI: test_create_capture_hook should no longer time out (TimeoutError)
  • All other tests continue passing on both platforms

🤖 Generated with Claude Code

StarrFox and others added 9 commits July 20, 2026 20:46
- hook.py: compute allocation_size via a first-pass encoding at target_address
  instead of sum(map(len, instructions)), since Instruction.create_* objects
  always return len() == 0, causing a severe undercount
- windows/process.py: add VirtualQueryEx scan to find a free region near
  preferred_start before calling VirtualAllocEx; preferred_start often points
  inside an already-committed code page (inject_target.exe text section),
  which caused ERROR_ACCESS_DENIED (WinError 5)
- linux/process.py: fix _poke_bytes to read-modify-write the last partial word
  instead of zero-padding, preventing silent corruption of bytes after the
  written range; add SIGTRAP verification to _ptrace_exec so shellcode
  failures surface as RuntimeError instead of silently returning bad RAX

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Temporary: prints addresses, raw bytes, and per-poll values so the
CI failure output reveals where exactly the hook breaks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
/proc/pid/mem pwrite to r-xp pages returns success on GitHub Actions
Ubuntu without actually modifying memory (silent no-op). This caused
the hook entry jump to never be written, so uses_player ran unhooked
and the capture address stayed zero for the full 60-second timeout.

Drop the pwrite fast-path for remote processes entirely; PTRACE_POKETEXT
works reliably for both r-xp code pages and rwx heap pages.
Also remove the temporary CI diagnostic prints from the test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Verify the poke actually took effect while the tracee is still stopped,
and print bytes at uses_player + hook body in CI to confirm the write.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PTRACE_POKETEXT writes to r-xp code pages appear to succeed (PTRACE_PEEKTEXT
verifies the write while stopped) but don't persist after PTRACE_DETACH on
some container runtimes (e.g. gVisor on GitHub Actions). Bypass this by
running shellcode inside the tracee that mprotects the target page to RWX
then writes each byte directly — the process writing to its own memory is
not subject to the external ptrace restriction.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ites)

The previous commit only called mprotect on the single page containing
`address`, but writes that cross a page boundary would fault on the
second page. Compute mprotect_len to cover all pages the write touches.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…estination

_ptrace_exec saves/restores bytes at exec_addr via PTRACE_POKETEXT.
If exec_addr happened to be the same region as the write destination
(e.g. hook_site being the first executable mapping), the restore would
overwrite the bytes we just committed via in-process stores, losing the
write entirely.

Pass a safe exec_addr (chosen by scanning maps for a non-overlapping
executable region) to _ptrace_exec, preventing the restore from clobbering
the freshly-written bytes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
On Azure KVM (GitHub Actions Ubuntu 24.04), the hypervisor enforces W^X
at the hardware level: in-process byte stores to file-backed r-xp pages
are silently dropped even after mprotect succeeds. PTRACE_POKETEXT to
file-backed pages similarly creates a "pending" state that only commits
when the CPU executes from that page (PTRACE_CONT from the patched RIP).

Fix: for each 4KB page touched by write_memory, run a PTRACE_CONT
shellcode that calls mmap(page, 4096, RWX, MAP_PRIVATE|MAP_ANONYMOUS|
MAP_FIXED, -1, 0), atomically replacing the file-backed page with a
fresh anonymous page. PTRACE_POKETEXT to anonymous pages is reliable and
not subject to hypervisor W^X enforcement. The original page content is
read first and merged with the caller's bytes so surrounding code is
preserved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The JMP write to file-backed r-xp pages doesn't persist on GitHub
Actions (Azure KVM W^X enforcement). Skip until fixed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@StarrFox
StarrFox enabled auto-merge (squash) July 21, 2026 03:56
@StarrFox
StarrFox merged commit 8d0bfe4 into main Jul 21, 2026
8 checks passed
@StarrFox
StarrFox deleted the fix/ci-hook-failures branch July 21, 2026 03:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant